/*-
 * Generates code for the expressions.
 *
 * Copyright (c) 2010 Alex Suhan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer
 * in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.io.PrintStream;
import java.util.ListIterator;

class ExpressionCode
{
    static PrintStream printer = System.out;
    
    private static void code(ScalaMethodCall expr, CodeSection ctx)
    {
        codeGenericDispatch(expr, ctx);
    }
    
    private static void code(ScalaThis expr, CodeSection ctx)
    {
        String resultReg = CgenSupport.VR(ctx.crtRegIdx);
        CgenSupport.emitIRMov(resultReg, CgenSupport.IR_SELF, printer);
        CgenSupport.emitPushRegStack(1, ctx);
    }
    
    private static void code(ScalaStringConst expr, CodeSection ctx)
    {
        int strIdx =
            ScalaTreeChecker.stringTable.get(expr.value);
        String strLabel = CgenConstants.strConstPrefix + strIdx;
        String stringReg = CgenSupport.VR(ctx.crtRegIdx);
        CgenSupport.emitIRLoadAddress(stringReg, strLabel, printer);
        CgenSupport.emitPushRegStack(1, ctx);
    }
    
    private static void code(ScalaIntConst expr, CodeSection ctx)
    {
        String intReg = CgenSupport.VR(ctx.crtRegIdx);
        CgenSupport.emitIRMov(intReg, "" + expr.value, printer);
        CgenSupport.emitPushRegStack(1, ctx);
    }
    
    private static void code(ScalaBoolConst expr, CodeSection ctx)
    {
        code(new ScalaIntConst(expr.value ? 1 : 0), ctx);
    }
    
    private static void code(ScalaBlock expr, CodeSection ctx)
    {
        ListIterator<ScalaExpression> exprIt = expr.list.listIterator();
        ScalaExpression fstExpr = exprIt.next();
        code(fstExpr, ctx);
        
        while (exprIt.hasNext()) {
            // each instruction pops the old value and pushes its value
            CgenSupport.emitPopRegStack(1, ctx);
            ScalaExpression crtExpr = exprIt.next();
            code(crtExpr, ctx);
        }
    }
    
    private static void code(ScalaWhile expr, CodeSection ctx)
    {
        printer.println("\t## begin loop");
        
        // generate and emit start label
        int labelIdx = ctx.getUniqueLoopIdx();
        printer.println(CgenSupport.loop_start + labelIdx + ":");

        // generate predicate code; the resulting object will be stored
        // in the first available register
        int     predIdx = ctx.crtRegIdx;
        String  predReg = CgenSupport.VR(predIdx);
        
        code(expr.condition, ctx);
        // +1 -> predReg
        
        // pop the stack, the predicate will be overriden by the body value
        CgenSupport.emitPopRegStack(1, ctx);
        // jump to end if the predicate value is false
        CgenSupport.emitIRJumpf(predReg, CgenSupport.loop_end + labelIdx, printer);
        code(expr.body, ctx);
        // +1 -> predReg; contains the body value
        
        CgenSupport.emitIRJump(CgenSupport.loop_start + labelIdx, printer);
        printer.println(CgenSupport.loop_end + labelIdx + ":");
    }
    
    private static void code(ScalaIfThenElse expr, CodeSection ctx)
    {
        printer.println("\t## begin if-then-else");
        
        // generate predicate code
        int     predIdx = ctx.crtRegIdx;
        String  predReg = CgenSupport.VR(predIdx);
        
        code(expr.condition, ctx);
        // +1 -> predReg

        int labelIdx = ctx.getUniqueIteLabelIdx();

        // decide
        CgenSupport.emitIRJumpf(predReg, CgenSupport.ite_false + labelIdx, printer);

        printer.println("\t## begin true branch");
        // true branch: pop the stack, the predicate object is needed no more
        // and it will be overriden by the branch value
        CgenSupport.emitPopRegStack(1, ctx);
        code(expr.thenBranch, ctx);
        // +1 -> predReg; now contains the true branch value
        CgenSupport.emitIRJump(CgenSupport.ite_end + labelIdx, printer);

        printer.println("\t## begin false branch");
        // false branch - see above how it works
        CgenSupport.emitPopRegStack(1, ctx);
        printer.println(CgenSupport.ite_false + labelIdx + ":");
        code(expr.elseBranch, ctx);
        printer.println(CgenSupport.ite_end + labelIdx + ":");
    }
    
    static void code(ScalaNew expr, CodeSection ctx)
    {
        printer.println("\t## begin new");

        // result register
        String instReg = CgenSupport.VR(ctx.crtRegIdx);

        if (!expr.type.equals(ScalaConstants.IntegerClassname) && !expr.type.equals(ScalaConstants.BooleanClassname)) {
            CgenSupport.emitIRLoadAddress(instReg, expr.type + IRLingo.protoSuffix, printer);
            CgenSupport.emitIRMov("VI0", instReg, printer);
            CgenSupport.emitIRCall(CgenSupport.OBJCOPY, printer);
            CgenSupport.emitIRMov(instReg, "VI0", printer);
            CgenSupport.emitIRCall(expr.type + IRLingo.initSuffix, printer);
            CgenSupport.emitPushRegStack(1, ctx);
        }
        else {
            CgenSupport.emitIRMov(instReg, "0", printer);
            CgenSupport.emitPushRegStack(1, ctx);
        }
    }
    
    private static void code(ScalaBinary expr, CodeSection ctx)
    {
        if (expr.isRelational())
            OpHelpers.codeIRRelOp(expr.lhs, expr.rhs, ctx, expr.op);
        else
            OpHelpers.codeIRArithOp(expr.lhs, expr.rhs, ctx, expr.op);
    }
    
    private static void code(ScalaUnary expr, CodeSection ctx)
    {
        switch (expr.op) {
            case NOT:
                String opReg = CgenSupport.VR(ctx.crtRegIdx);
                code(expr.operand, ctx);
                // +1 -> opReg
                CgenSupport.emitIRSub(opReg, "1", opReg, printer);
                break;
            default:
                break;
        }
    }
    
    private static void code(ScalaObject expr, CodeSection ctx)
    {
        String objReg = CgenSupport.VR(ctx.crtRegIdx);
        
        switch (expr.idScope) {
            case PARAMETER:
                ScalaFormals formals = ctx.crtMethodInfo.getFormalParams();
                int inRegIdx = 0;
                for (ScalaFormal crtParam: formals) {
                    if (crtParam.getName().equals(expr.id)) {
                        String rhsReg = CgenSupport.VR(inRegIdx);
                        // push the result on the register stack
                        CgenSupport.emitIRMov(objReg, rhsReg, printer);
                        // protect the copy
                        CgenSupport.emitPushRegStack(1, ctx);
                        return;
                    }
                    inRegIdx++;
                }
                break;
            case LOCAL:
                ScalaClassLayout classLayout =
                    ctx.ctx.getClassLayout(ctx.crtClassName);
                ScalaClassLayout.CgenMethodInfo methodLayout =
                    classLayout.methodOffset.get(ctx.crtMethodName);
                int off = methodLayout.getLocalVarOffset(expr.id);
                CgenSupport.emitIRMov(objReg, CgenSupport.VR(off), printer);
                CgenSupport.emitPushRegStack(1, ctx);
                break;
            case CLASS:
                ScalaClassLayout crtLayout =
                    ctx.ctx.getClassLayout(ctx.crtClassName);
                // attribute metadata stores the offset relative to self;
                CgenSupport.emitIRLoad(objReg, crtLayout.attrOffset.get(expr.id),
                                       CgenSupport.IR_SELF, printer);
                // protect the copy
                CgenSupport.emitPushRegStack(1, ctx);
                break;
        }
    }
    
    private static void code(ScalaAssign expr, CodeSection ctx)
    {
        printer.println("\t## begin assign");
        
        int rhsIdx = ctx.crtRegIdx;
        String rhsReg = CgenSupport.VR(rhsIdx);
        String tempReg = CgenSupport.VR(rhsIdx + 1);

        // generate code for the right side
        code(expr.rhs, ctx);
        // +1 -> rhsReg
        
        switch (expr.scope) {
            case PARAMETER:
                ScalaFormals formals = ctx.crtMethodInfo.getFormalParams();
                int paramIdx = 0;
                for (ScalaFormal crtParam: formals) {
                    if (crtParam.getName().equals(expr.lhs)) {
                        // box integer if needed (upcast to Object)
                        if (expr.rhs.typeName().equals(ScalaConstants.IntegerClassname) &&
                            !crtParam.getType().equals(ScalaConstants.IntegerClassname)) {
                            CgenSupport.emitBoxInt(rhsReg, tempReg, printer);
                            CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                        }
                        else if (expr.rhs.typeName().equals(ScalaConstants.BooleanClassname) &&
                                 !crtParam.getType().equals(ScalaConstants.BooleanClassname)) {
                            CgenSupport.emitBoxBool(rhsReg, tempReg, printer);
                            CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                        }
                        // assign the method parameter
                        String lhsReg = CgenSupport.VR(paramIdx);
                        printer.println("\t## store rhs from " + rhsReg + " to parameter " + crtParam.getName() + " in " + lhsReg);
                        CgenSupport.emitIRMov(lhsReg, rhsReg, printer);
                        return;
                    }
                    paramIdx++;
                }
                break;
            case LOCAL:
            {
                ScalaClassInfo classInfo =
                    ScalaMapsBuilder.classTable.get(ctx.crtClassName);
                ScalaMethodInfo methodInfo =
                    classInfo.getMethod(ctx.crtMethodName);
                ScalaClassLayout classLayout =
                    ctx.ctx.getClassLayout(ctx.crtClassName);
                ScalaClassLayout.CgenMethodInfo methodLayout =
                    classLayout.methodOffset.get(ctx.crtMethodName);
                ScalaLocalVar localVar = methodInfo.getLocalVar(expr.lhs);
                // box if needed (upcast to Object), then store relative to self
                if (expr.rhs.typeName().equals(ScalaConstants.IntegerClassname) &&
                    !localVar.typeName().equals(ScalaConstants.IntegerClassname)) {
                    CgenSupport.emitBoxInt(rhsReg, tempReg, printer);
                    CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                }
                else if (expr.rhs.typeName().equals(ScalaConstants.BooleanClassname) &&
                    !localVar.typeName().equals(ScalaConstants.BooleanClassname)) {
                    CgenSupport.emitBoxBool(rhsReg, tempReg, printer);
                    CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                }
                printer.println("\t## store rhs in " + rhsReg + " to local var " + expr.lhs);
                int offset = methodLayout.getLocalVarOffset(expr.lhs);
                CgenSupport.emitIRMov(CgenSupport.VR(offset), rhsReg, printer);
                break;
            }
            case CLASS:
            {
                ScalaClassInfo classInfo =
                    ScalaMapsBuilder.classTable.get(ctx.crtClassName);
                ScalaAttrInfo attrInfo = classInfo.getAttribute(expr.lhs);
                // attribute; box if needed (upcast to Object), then store relative to self
                if (expr.rhs.typeName().equals(ScalaConstants.IntegerClassname) &&
                    !attrInfo.getType().equals(ScalaConstants.IntegerClassname)) {
                    CgenSupport.emitBoxInt(rhsReg, tempReg, printer);
                    CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                }
                else if (expr.rhs.typeName().equals(ScalaConstants.BooleanClassname) &&
                    !attrInfo.getType().equals(ScalaConstants.BooleanClassname)) {
                    CgenSupport.emitBoxBool(rhsReg, tempReg, printer);
                    CgenSupport.emitIRMov(rhsReg, tempReg, printer);
                }
                printer.println("\t## store rhs in " + rhsReg + " to attribute " + expr.lhs);
                ScalaClassLayout crtLayout =
                    ctx.ctx.getClassLayout(ctx.crtClassName);
                int offset = crtLayout.attrOffset.get(expr.lhs);
                CgenSupport.emitIRStore(rhsReg, offset, CgenSupport.IR_SELF, printer);
                break;
            }
        }
    }
    
    private static void code(ScalaLocalVar expr, CodeSection ctx)
    {
        printer.println("\t## begin local variable");

        ScalaClassLayout classLayout =
            ctx.ctx.getClassLayout(ctx.crtClassName);
        ScalaClassLayout.CgenMethodInfo methodLayout =
            classLayout.methodOffset.get(ctx.crtMethodName);
        String localVarReg =
            CgenSupport.VR(methodLayout.getLocalVarOffset(expr.name));
        
        String initReg = CgenSupport.VR(ctx.crtRegIdx);
        
        // initialize the variable
        if (expr.init != null) {
            // generate code for init expression
            code(expr.init, ctx);
            // +1 -> initReg
            CgenSupport.emitIRMov(localVarReg, initReg, printer);
        } else if (expr.type.equals(ScalaConstants.StringClassname)) {
            printer.println("\t## create string local variable - not in Scala source");
            code(new ScalaNew(expr.type), ctx);
            // +1 -> initReg
            CgenSupport.emitIRMov(localVarReg, initReg, printer);
        }
        else {
            CgenSupport.emitIRMov(localVarReg, "0", printer);
            CgenSupport.emitPushRegStack(1, ctx);
        }
    }
    
    // BROKEN - this is slightly broken; if some class inherits from IO
    //          and overrides out_int, we're in serious trouble; I'll fix ASAP
    private static boolean mustBoxParameters(ScalaClassLayout callerClass, int dispatchIdx) {
        boolean boxParams = false;
        ScalaClassLayout crtInh = callerClass;

        while (crtInh != null) {
            if (crtInh.name.equals(ScalaConstants.ConsoleClassname)) {
                boxParams = (dispatchIdx == crtInh.methodOffset.get("out_int").offset);
                break;
            } else if (crtInh.name.equals(ScalaConstants.StringClassname)) {
                boxParams = (dispatchIdx == crtInh.methodOffset.get("substr").offset);
                break;
            }

            crtInh = crtInh.parent;
        }
        
        return boxParams;
    }
    
    // BROKEN - see above
    private static boolean mustUnboxReturned(ScalaClassLayout callerClass, int dispatchIdx) {
        boolean unboxReturned = false;
        ScalaClassLayout crtInh = callerClass;

        while (crtInh != null) {
            if (crtInh.name.equals(ScalaConstants.ConsoleClassname)) {
                unboxReturned = (dispatchIdx == crtInh.methodOffset.get("in_int").offset);
                break;
            } else if (crtInh.name.equals(ScalaConstants.StringClassname)) {
                unboxReturned = (dispatchIdx == crtInh.methodOffset.get("length").offset);
                break;
            }

            crtInh = crtInh.parent;
        }
        
        return unboxReturned;
    }
    
    private static void handleDispatchToVoid(CodeSection ctx, String callerReg, String notVoidLabel, int lineNumber, PrintStream s) {
        s.println("\t## dispatch to void handling, ends at " + notVoidLabel);
        
        String  temp = CgenSupport.VR(ctx.crtRegIdx);
        // skip the error handling if caller not void
        CgenSupport.emitIRJumpt(callerReg, notVoidLabel, s);
        // file name in the first input register
        String fileName =
            ScalaMapsBuilder.classTable.get(ctx.crtClassName).getFilename();
        int strIdx = ScalaTreeChecker.stringTable.get(fileName);
        CgenSupport.emitIRMov(temp, CgenConstants.strConstPrefix + strIdx, s);
        CgenSupport.emitIRMov("VI0", temp, s);
        // line number in the second input register
        CgenSupport.emitIRMov(temp, "" + lineNumber, s);
        CgenSupport.emitIRMov("VI1", temp, s);
        CgenSupport.emitIRCall(IRLingo.void_disp_handler, s);
        printer.print(notVoidLabel + ":\n");
    }
    
    static void codeGenericDispatch(ScalaMethodCall expr, CodeSection ctx) {
        printer.println("\t## begin dispatch");
        
        // caller reg - will store the caller and it'll be overriden
        //              by the dispatch result
        int     callerIdx = ctx.crtRegIdx;
        String  callerReg = CgenSupport.VR(callerIdx);

        // spare storage into a certainly free register
        String  tempReg = CgenSupport.VR(ctx.crtRegIdx + 1);

        // generate code for the caller expression; the result
        // will be into the callerReg
        code(expr.caller, ctx);
        // +1 -> callerReg
        
        printer.println("\t## caller in " + callerReg);

        if (expr.caller.typeName().equals(ScalaConstants.IntegerClassname)) {
            CgenSupport.emitBoxInt(callerReg, tempReg, printer);
            CgenSupport.emitIRMov(callerReg, tempReg, printer);
        }
        else if (expr.caller.typeName().equals(ScalaConstants.BooleanClassname)) {
            CgenSupport.emitBoxBool(callerReg, tempReg, printer);
            CgenSupport.emitIRMov(callerReg, tempReg, printer);
        }

        // handle runtime dispatch to void error
        String notVoidLabel =
            CgenSupport.dispatch_notvoid + ctx.getUniqueDispatchLabelIdx();
        handleDispatchToVoid(ctx, callerReg, notVoidLabel, expr.getLine(), printer);

        String callerTypeName = expr.caller.typeName();
        
        if (callerTypeName.equals(ScalaConstants.ThisClassName))
            callerTypeName = ctx.crtClassName;
        
        // find out the dispatch index; 'callerClass' is obviously
        // the reference type, not the accurate, runtime decided type,
        // but the dispatch index is still the same
        ScalaClassLayout callerClass = ctx.ctx.inhTree.get(callerTypeName);
        int dispatchIdx = callerClass.methodOffset.get(expr.name).offset;
        
        // check if compatible with IO or String
        boolean boxParams = mustBoxParameters(callerClass, dispatchIdx);
        boolean unboxReturned = mustUnboxReturned(callerClass, dispatchIdx);

        // generate code for actual parameters
        int fstRegIdx = ctx.crtRegIdx; // the first VR for actuals
        int argCount = 0;
        
        ScalaExpressionList actuals = expr.actual;
        ScalaFormals formals = ScalaTreeChecker.getMethod(expr.name, callerClass.name).getFormalParams();
        ListIterator<ScalaFormal> formalIt = formals.listIterator();
        
        // must have all the parameters before assigning them to VI'printer
        // because of the nested calls
        for (ScalaExpression crtActual: actuals) {
            argCount++;
            ScalaFormal crtFormal = formalIt.next();
            
            String actualReg = CgenSupport.VR(ctx.crtRegIdx);
            tempReg = CgenSupport.VR(ctx.crtRegIdx + 1);
            
            printer.println("\t## actual parameter " + argCount);
            
            code(crtActual, ctx);
            // +1 +#argCount
            
            if (crtActual.typeName().equals(ScalaConstants.IntegerClassname) && (boxParams || !crtFormal.getType().equals(ScalaConstants.IntegerClassname))) {
                CgenSupport.emitBoxInt(actualReg, tempReg, printer);
                CgenSupport.emitIRMov(actualReg, tempReg, printer);
            }
            else if (crtActual.typeName().equals(ScalaConstants.BooleanClassname) && (boxParams || !crtFormal.getType().equals(ScalaConstants.BooleanClassname))) {
                CgenSupport.emitBoxBool(actualReg, tempReg, printer);
                CgenSupport.emitIRMov(actualReg, tempReg, printer);
            }
        }
        
        printer.println("\t## store self and actual parameters into VI's");
        
        // put caller into first input register
        CgenSupport.emitIRMov("VI0", callerReg, printer);
        // put arguments in the next input registers
        for (int inRegIdx = 0; inRegIdx < argCount; inRegIdx++) {
            String crtArg = CgenSupport.VR(inRegIdx + fstRegIdx);
            CgenSupport.emitIRMov("VI" + (inRegIdx + 1), crtArg, printer);
            CgenSupport.emitPopRegStack(1, ctx);
        }
        
        // summary: +1 values on the register stack (the caller)
        
        printer.println("\t## compute the target address");
        
        // load into the first available register the dispatch table address
        String callAddrReg = CgenSupport.VR(ctx.crtRegIdx);
        // first, load the dispatch table
        CgenSupport.emitIRLoad(callAddrReg, 2, callerReg, printer);
        // now load the address at the dispatch index
        CgenSupport.emitIRLoad(callAddrReg, dispatchIdx, callAddrReg, printer);
        
        // call...
        CgenSupport.emitIRCall(callAddrReg, printer);
        
        printer.println("\t## fetch (and maybe unbox) the result");
        
        // ... and store the result back into the callerReg
        CgenSupport.emitIRMov(callerReg, "VI0", printer);

        // shall we unbox?
        if (unboxReturned) {
            tempReg = CgenSupport.VR(ctx.crtRegIdx);
            CgenSupport.emitUnboxInt(callerReg, tempReg, printer);
            CgenSupport.emitIRMov(callerReg, tempReg, printer);
        }
        
        // +1 -> callerReg; the result is safely at the top of the stack
    }
    
    static void code(ScalaExpression expr, CodeSection ctx)
    {
        if (expr instanceof ScalaStringConst)
            code((ScalaStringConst) expr, ctx);
        else if (expr instanceof ScalaIntConst)
            code((ScalaIntConst) expr, ctx);
        else if (expr instanceof ScalaMethodCall)
            code((ScalaMethodCall) expr, ctx);
        else if (expr instanceof ScalaBoolConst)
            code((ScalaBoolConst) expr, ctx);
        else if (expr instanceof ScalaThis)
            code((ScalaThis) expr, ctx);    
        else if (expr instanceof ScalaNew)
            code((ScalaNew) expr, ctx);
        else if (expr instanceof ScalaBinary)
            code((ScalaBinary) expr, ctx);
        else if (expr instanceof ScalaBlock)
            code((ScalaBlock) expr, ctx);
        else if (expr instanceof ScalaIfThenElse)
            code((ScalaIfThenElse) expr, ctx);
        else if (expr instanceof ScalaWhile)
            code((ScalaWhile) expr, ctx);
        else if (expr instanceof ScalaUnary)
            code((ScalaUnary) expr, ctx);
        else if (expr instanceof ScalaObject)
            code((ScalaObject) expr, ctx);
        else if (expr instanceof ScalaAssign)
            code((ScalaAssign) expr, ctx);
        else if (expr instanceof ScalaLocalVar)
            code((ScalaLocalVar) expr, ctx);
    }
    
    static class OpHelpers {

        static final byte PLUS = 0;
        static final byte SUB = 1;
        static final byte MUL = 2;
        static final byte DIV = 3;
        static final byte LT = 4;
        static final byte LEQ = 5;
        static final byte EQ = 6;

        /**
         * Arithmetical operations IR code generator helper.
         * @param ctx        - the method meta information
         * @param printer    - the output stream
         * @param lhs         - the lhs expression
         * @param rhs         - the rhs expression
         * @param op         - the operation code
         */
        static void codeIRArithOp(ScalaExpression lhs, ScalaExpression rhs,
                                  CodeSection ctx, ScalaConstants.ScalaBinaryOp op)
        {
            // generate code for the two expressions
            String  lhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(lhs, ctx);
            // +1 -> lhsReg
            String  rhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(rhs, ctx);
            // +2 -> rhsReg, lhsReg

            // the right-hand side won't be needed anymore
            CgenSupport.emitPopRegStack(1, ctx);
            // +1

            switch (op) {
                case ADD:
                    CgenSupport.emitIRAdd(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case SUB:
                    CgenSupport.emitIRSub(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case MUL:
                    CgenSupport.emitIRMul(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case DIV:
                    CgenSupport.emitIRDiv(lhsReg, lhsReg, rhsReg, printer);
                    break;
                default:
                    break;
            }
        }

        static void codeIRRelOp(ScalaExpression lhs, ScalaExpression rhs,
                                CodeSection ctx, ScalaConstants.ScalaBinaryOp op)
        {
            // the equality test is not useful for non-string because of boxing
            if (lhs.typeName().equals(ScalaConstants.StringClassname)) {
                codeIREqStr(lhs, rhs, ctx);
                return;
            }

            String lhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(lhs, ctx);
            // +1 -> lhsReg
            String rhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(rhs, ctx);
            // +2 -> rhsReg, lhsReg

            // the right-hand side won't be needed anymore
            CgenSupport.emitPopRegStack(1, ctx);
            // +1

            switch (op) {
                case LT:
                    CgenSupport.emitIRLt(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case LE:
                    CgenSupport.emitIRLeq(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case EQ:
                    CgenSupport.emitIREq(lhsReg, lhsReg, rhsReg, printer);
                    break;
                case GE:
                    CgenSupport.emitIRLeq(lhsReg, rhsReg, lhsReg, printer);
                case GT:
                    CgenSupport.emitIRLt(lhsReg, rhsReg, lhsReg, printer);
                    break;
                default:
                    break;
            }
        }

        // compares two strings for content equality
        private static void codeIREqStr(ScalaExpression lhs, ScalaExpression rhs,
                                        CodeSection ctx)
        {
            String resultReg = CgenSupport.VR(ctx.crtRegIdx);
            CgenSupport.emitPushRegStack(1, ctx);
            // +1 -> objReg
            String lhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(lhs, ctx);
            // +2
            String rhsReg = CgenSupport.VR(ctx.crtRegIdx);
            ExpressionCode.code(rhs, ctx);
            // +3

            String tempReg = CgenSupport.VR(ctx.crtRegIdx);

            CgenSupport.emitIRMov(tempReg, "1", printer);
            CgenSupport.emitIRMov("VI0", tempReg, printer);

            CgenSupport.emitIRMov(tempReg, "0", printer);
            CgenSupport.emitIRMov("VI1", tempReg, printer);

            CgenSupport.emitIRMov("VI2", lhsReg, printer);
            CgenSupport.emitIRMov("VI3", rhsReg, printer);

            CgenSupport.emitPopRegStack(2, ctx);
            // +1

            CgenSupport.emitIRCall(CgenSupport.EQ_TST_LABEL, printer);
            CgenSupport.emitIRMov(resultReg, "VI0", printer);
        }
    }
}
